package br.usp.icmc.biomac.client.view.impl;


import java.util.ArrayList;
import java.util.List;
import java.util.Date;

import br.usp.icmc.biomac.shared.Coordinate;
import br.usp.icmc.biomac.shared.EPadLogger;
import br.usp.icmc.biomac.shared.ROI;
import br.usp.icmc.biomac.shared.ROIShape;

import com.google.gwt.event.dom.client.ClickEvent;  
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.HasClickHandlers;
import com.google.gwt.event.dom.client.HasKeyPressHandlers;
import com.google.gwt.event.dom.client.HasMouseDownHandlers;
import com.google.gwt.event.dom.client.HasMouseMoveHandlers;
import com.google.gwt.event.dom.client.HasMouseUpHandlers;
import com.google.gwt.event.dom.client.HasMouseWheelHandlers;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.dom.client.MouseDownEvent;
import com.google.gwt.event.dom.client.MouseDownHandler;
import com.google.gwt.event.dom.client.MouseMoveEvent;
import com.google.gwt.event.dom.client.MouseMoveHandler;
import com.google.gwt.event.dom.client.MouseOutEvent;
import com.google.gwt.event.dom.client.MouseOutHandler;
import com.google.gwt.event.dom.client.MouseUpEvent;
import com.google.gwt.event.dom.client.MouseUpHandler;
import com.google.gwt.event.dom.client.MouseWheelEvent;
import com.google.gwt.event.dom.client.MouseWheelHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.DOM;
import com.hydro4ge.raphaelgwt.client.Attr;
import com.hydro4ge.raphaelgwt.client.BBox;
import com.hydro4ge.raphaelgwt.client.Raphael; 



/**
 * Drawing handles drawing clickable shapes on the SVG paper
 * @author debra willrett
 *
 */
public class Drawing extends Raphael implements HasMouseDownHandlers, HasMouseUpHandlers, HasMouseMoveHandlers, HasMouseWheelHandlers, HasKeyPressHandlers{
	
	// logging
	private static final EPadLogger logger = EPadLogger.getLogger();
	
	// the drawing attributes and ghosting variables
	private Attr lineAttr = new Attr();
	private Attr labelAttr = new Attr();
	private Attr labelContainerAttr = new Attr();
	
	// scrolling between images
	private List<Set> drawingSets;
	private List<Set> labelSets;
	private int activeDrawing = 0;
	private int sizeDrawing = 0;
	
	// selection, drawing and ghosting
	private Shape selected = null;
	private Shape ghost = null;
	private ROIShape drawingShape = null;
	private String ghostPath = "";
	private boolean drawMode = false;
	private static final int MinDelta = 10;
	private List<Coordinate> coords;
		
	// two coordinate spaces, the window and the drawing
	private double windowWidth = 512;
	private double windowHeight = 512;
	private double imageWidth = 512;
	private double imageHeight = 512;
	private double scale = 1.0;
	private double lastx= 0;
	private double lasty = 0;
	
	/**
	 * 
	 * a label for the drawing shape
	 * 
	 */
	public class Label  extends Raphael.Text  {
		  //private Text labelText;
		  private Rect labelBox;
		  private Shape owner;
		  private double x;
		  private double y;
		  private String text;

		  // draw a label on the shape
		  public Label(double x, double y, String text) {
		      super(x, y, text);
		  }
		  
		  
		  public Label(double x, double y, String text, Shape owner) {
			  super(x, y, text);

			  this.x = x;
			  this.y = y;

			  this.owner = owner;
			  this.text = text;
			  this.attr(labelAttr);
			  BBox box = this.getBBox();
			  
			  double w = box.width();
			  double h = box.height();
			  //this.labelBox = new Rect(x,y,w,h);
		 }


			public void setLabel(String text) {
				this.text = text;
			}

			public String getLabel() {
				return this.text;
			}
		
		public void position(double x, double y) {
			logger.info("position " + x + y);
			this.x = x;
			this.y = y;
			Attr a = new Attr();
			a.x(x);
			a.y(y);
			this.attr(a);

		}
    }
	  
	  
	  /**
	   * a clickable line
	   */
	  @SuppressWarnings("serial")
	  public class Line extends Raphael.Path implements HasClickHandlers {
		 
		public Label label;
		private List<Coordinate> coords;
		private Date date; 
		private int length;
		  
	    public Line(double fromx, double fromy, double tox, double toy) {
	      super("M" + fromx + " " + fromy + "L" + tox + " " + toy);
	      coords = new ArrayList<Coordinate>();
		  coords.add(new Coordinate((int) fromx, (int) fromy));
		  coords.add(new Coordinate((int) tox, (int) toy)); 
		  this.label = new Label(tox, toy, "line label");

	    }
	   

	    public HandlerRegistration addClickHandler(ClickHandler handler) {
	      return this.addDomHandler(handler, ClickEvent.getType());
	    }

		public List<Coordinate> getCoordinates() {
			return coords;
		}

		public void setCoordinates(List<Coordinate> coords) {
			this.coords = coords;
		}
		
		public void setDateTime(Date date) {
			this.date = date;
		}
		
		public Date getDateTime() {
			return date;
			
		}


		public void setLabel(String label) {
			
			this.label.setLabel(label);
		}


		public String getLabel() {
	
			return this.label.getLabel(); 
		}
		
		
		
		 public void setLabelShape(Label label) {
				this.label = label;
		    }
		 
		 public Label getLabelShape() {
				return this.label;
		    }

	  }


	  /**
	   * a clickable rect
	   */
	  public class Rect extends Raphael.Rect
	      implements HasClickHandlers
	  {
	  
	    
		public Label label;
		private List<Coordinate> coords;
		private Date date; 
		private int length;
			
		public Rect(double x, double y, double w, double h) {
			super(x, y, w, h);
	      coords = new ArrayList<Coordinate>();
		  coords.add(new Coordinate((int) x, (int) y));
		  coords.add(new Coordinate((int) (x + w), (int) (y + h))); 
		  this.label = new Label(x, y, "rect label");
	    }
		
		
	    public HandlerRegistration addClickHandler(ClickHandler handler) {
		      return this.addDomHandler(handler, ClickEvent.getType());
		    }

		 
			public void setCoordinates(List<Coordinate> coordinates) {
				coords = coordinates;
			}

		
			public List<Coordinate> getCoordinates() {
				return coords;
			}

	
			public void setLabel(String text) {
				this.label.setLabel(text);
			}

	
			public String getLabel() {
				return this.label.getLabel();
			}

		
			public void setDateTime(Date date) {
				this.date = date;
			}

		
			public Date getDateTime() {
				return date;
			}
			
			public void setLabelShape(Label label) {
				this.label = label;
		    }
		 
			public Label getLabelShape() {
				return this.label;
		    }
	  }
	  

		  
	  /**
	   * a clickable circle
	   */
	  public class Circle extends Raphael.Circle
	      implements HasClickHandlers
	  {
	    
	    
	    private Label label;
	    private Double cx;
	    private Double cy;
	    private Double r;
		private Date date;
		private List<Coordinate> coords;
	
		public Circle(double x, double y, double node_size) {
		      super(x, y, node_size);
		      this.label = new Label(x, y, "circle label");
		      this.cx = x;
		      this.cy = y;
		      this.r = node_size;
		      this.coords = new ArrayList<Coordinate>();
			  this.coords.add(new Coordinate((int) x, (int) y));
	    }

	    public HandlerRegistration addClickHandler(ClickHandler handler) {
	      return this.addDomHandler(handler, ClickEvent.getType());
	    }

	
		public void setCoordinates(List<Coordinate> coordinates) {
			coords = coordinates;
		}

	
		public List<Coordinate> getCoordinates() {
			return coords;
		}

	
		public void setLabel(String text) {
			this.label.setLabel(text);
		}


		public String getLabel() {
			return this.label.getLabel();
		}


		public void setDateTime(Date date) {
			this.date = date;
		}


		public Date getDateTime() {
			return date;
		}
		
		public void setLabelShape(Label label) {
			this.label = label;
	    }
	 
		public Label getLabelShape() {
			return this.label;
	    }

	  }
	  
	  
	  /**
	   * a clickable ellipse
	   */
	  public class Ellipse extends Raphael.Ellipse
	      implements HasClickHandlers
	  {
	    
	    
		public Label label;
	    private Double cx;
	    private Double cy;
	    private Double rx;
	    private Double ry;
		private Date date;
		private List<Coordinate> coords;
	
			
		public Ellipse(double x, double y, double sizex, double sizey) {
		      super(x, y, sizex, sizey); 
		      this.label = new Label(x, y, "ellipse label");
		      this.cx = x;
		      this.cy = y;
		      this.rx = sizex;
		      this.ry = sizey;
		      this.coords = new ArrayList<Coordinate>();
			  this.coords.add(new Coordinate((int) x, (int) y));
	    }
		
		
	    public HandlerRegistration addClickHandler(ClickHandler handler) {
		      return this.addDomHandler(handler, ClickEvent.getType());
		    }

		
			public void setCoordinates(List<Coordinate> coordinates) {
				coords = coordinates;
			}

	
			public List<Coordinate> getCoordinates() {
				return coords;
			}

	
			public void setLabel(String text) {
				this.label.setLabel(text);
			}

	
			public String getLabel() {
				return this.label.getLabel();
			}

	
			public void setDateTime(Date date) {
				this.date = date;
			}

	
			public Date getDateTime() {
				return date;
			}
			
			public void setLabelShape(Label label) {
				this.label = label;
		    }
		 
			public Label getLabelShape() {
				return this.label;
		    }
	  }
	  
	  /**
	   * a clickable arrow
	   */
	  public class Arrow extends Raphael.Path
	      implements HasClickHandlers
	  {
		private Label label;
		private List<Coordinate> coords;
		private Date date; 
		private int length;
			
		public Arrow(double fromx, double fromy, double tox, double toy) {
	      super("M" + fromx + " " + fromy + "L" + tox + " " + toy);
	      coords = new ArrayList<Coordinate>();
		  coords.add(new Coordinate((int) fromx, (int) fromy));
		  coords.add(new Coordinate((int) tox, (int) toy)); 
		  this.label = new Label(fromx, fromy, "arrow label");
	    }
		
	    public HandlerRegistration addClickHandler(ClickHandler handler) {
		      return this.addDomHandler(handler, ClickEvent.getType());
		    }


			public void setCoordinates(List<Coordinate> coordinates) {
				coords = coordinates;
			}

		
			public List<Coordinate> getCoordinates() {
				return coords;
			}

	
			public void setLabel(String text) {
				this.label.setLabel(text);
			}


			public String getLabel() {
				return this.label.getLabel();
			}

	
			public void setDateTime(Date date) {
				this.date = date;
			}

		
			public Date getDateTime() {
				return date;
			}
			
			public void setLabelShape(Label label) {
				this.label = label;
		    }
		 
			public Label getLabelShape() {
				return this.label;
		    }
	  }
	  
	  	  
	  /**
	   * a clickable open poly
	   */
	  public class OpenPoly extends Raphael.Path
	      implements HasClickHandlers
	  {
	   
	    
		public Label label;
		private List<Coordinate> coords;
		private Date date; 
		private int length;
		  
		public OpenPoly(double fromx, double fromy) {
		  super("M" + fromx + " " + fromy );
	      coords = new ArrayList<Coordinate>();
		  coords.add(new Coordinate((int) fromx, (int) fromy));
		  this.label = new Label(fromx, fromy, "open poly label");
		}
		
	    public HandlerRegistration addClickHandler(ClickHandler handler) {
		      return this.addDomHandler(handler, ClickEvent.getType());
		    }

	
			public void setCoordinates(List<Coordinate> coordinates) {
				coords = coordinates;
			}

	
			public List<Coordinate> getCoordinates() {
				return coords;
			}

	
			public void setLabel(String label) {
				this.label.setLabel(label);
			}

	
			public String getLabel() {
				return this.label.getLabel();
			}

	
			public void setDateTime(Date date) {
				this.date = date;
			}


			public Date getDateTime() {
				return date;
			}
			
			public void setLabelShape(Label label) {
				this.label = label;
		    }
		 
			public Label getLabelShape() { 
				return this.label;
		    }
	  }
	  
	  /**
	   * a clickable closed poly
	   */
	  public class Poly extends Raphael.Path
	      implements HasClickHandlers
	  {
	    
	    
		public Label label;
		private List<Coordinate> coords;
		private Date date; 
		private int length;
		  
		public Poly(double fromx, double fromy) {
		      super("M" + fromx + " " + fromy );
	      coords = new ArrayList<Coordinate>();
		  coords.add(new Coordinate((int) fromx, (int) fromy));
		  this.label = new Label(fromx, fromy, "closed poly label");
		}
		

	    public HandlerRegistration addClickHandler(ClickHandler handler) {
		      return this.addDomHandler(handler, ClickEvent.getType());
		    }

	
			public void setCoordinates(List<Coordinate> coordinates) {
				coords = coordinates;
			}


			public List<Coordinate> getCoordinates() {
				return coords;
			}

	
			public void setLabel(String label) {
				this.label.setLabel(label);
			}

	
			public String getLabel() {
				return this.label.getLabel();
			}

	
			public void setDateTime(Date date) {
				this.date = date;
			}

		
			public Date getDateTime() {
				return date;
			}
			
			public void setLabelShape(Label label) {
				this.label = label;
		    }
		 
			public Label getLabelShape() {
				return this.label;
		    }
	  }
	  
	  /**
	   * a clickable text comment
	   */
	  public class Text extends Raphael.Text 
	      implements HasClickHandlers
	  {
		  
		public Label label;
		private List<Coordinate> coords;
		private Date date; 

			
	    public Text(double x, double y, String text) {
	      super(x, y, text);
	      coords = new ArrayList<Coordinate>();
		  coords.add(new Coordinate((int) x, (int) y));
		  this.label = new Label(x, y, "text label");
	      
	    }
	    
	    public HandlerRegistration addClickHandler(ClickHandler handler) {
		      return this.addDomHandler(handler, ClickEvent.getType());
		    }

		
			public void setCoordinates(List<Coordinate> coordinates) {
				coords = coordinates;
			}

	
			public List<Coordinate> getCoordinates() {
				return coords;
			}

	
			public void setLabel(String label) {
				this.label.setLabel(label);
			}


			public String getLabel() {
				return this.label.getLabel();
			}

	
			public void setDateTime(Date date) {
				this.date = date;
			}


			public Date getDateTime() {
				return date;
			}
			
			public void setLabelShape(Label label) {
				this.label = label;
		    }
		 
			public Label getLabelShape() {
				return this.label;
		    }
	  }
	  
	  
	
	    /**
	     * Drawing constructor
	     * @author debra willrett
	     * @param imageWidth - the width of the drawing
	     * @param imageHeight - the height of the drawing
	     * @param windowWidth - the width of the window
	     * @param windowHeight - the height of the window
	     * 
	     */
	    public Drawing(int imageWidth, int imageHeight, int windowWidth, int windowHeight, int  size, int active) {

	    	super(windowWidth, windowHeight);
	    	
	    	// set up the viewport, will use this for scaling down the road
	    	this.imageWidth = imageWidth;
	    	this.imageHeight = imageHeight;
	    	this.windowWidth = windowWidth;
	    	this.windowWidth = windowHeight;
	    	
	    	// set attributes for line drawing
			this.lineAttr.stroke("green");
			this.lineAttr.strokeWidth(3);
			
			this.labelAttr.fill("green");
			this.labelAttr.font("Helvetica");
			this.labelAttr.fontFamily("sans-serif");
			this.labelAttr.fontSize(14);
			this.labelAttr.fontWeight("normal");
			//this.labelAttr.strokeWidth(1);
			
			this.labelContainerAttr.fill("maroon");
			this.labelContainerAttr.fillOpacity(0.5);
			this.labelContainerAttr.strokeWidth(0);
			
		

			// build the drawing sets for scrolling
			this.activeDrawing = active;
			this.sizeDrawing = size;
			drawingSets = new ArrayList<Set> ();
			labelSets = new ArrayList<Set> ();
			for (int i=0; i< size; i++) {
				drawingSets.add( new Set());
				labelSets.add( new Set());
			}

	  }
	  
	  @Override
	  public void onLoad() {
	    super.onLoad();
	  }
	  
	  // not scaling yet
	  private void scaleCoordinate(double x, double y) {
		  x = Math.floor(x * this.scale);
		  y = Math.floor(y * this.scale);
		  
		}
	  
	  // not scaling yet
	  private void unscaleCoordinate(double x, double y) {
		  x = Math.floor(x / this.scale);
		  y = Math.floor(y / this.scale);
		   
		  
		}
	  /**
	   * module reset - called to clear and reset the SVG paper for a new set of drawings
	   * @param size - the number of drawings in the set
	   */
	  public void reset(int size) {
		  // reset the drawing
		  this.activeDrawing = 0;
		  this.sizeDrawing = size;
		  
		  // clear the SVG paper
		  this.clear();
		  
		  // rebuild the drawing sets, this is used for scrolling between drawings
		  drawingSets = new ArrayList<Set> ();
		  labelSets = new ArrayList<Set> ();
		  for (int i=0; i< size; i++) {
			  drawingSets.add( new Set());
			  labelSets.add( new Set());

		  }
		  
		  // make sure our drawing control variables are initialized
		  selected = null;
		  ghost= null;
		  drawingShape = null;
		  drawMode = false;
		}
	  
	  /**
	   * module select - selects the objects associated with a particular set, can be used for scrolling between drawings
	   * @param active - the active drawing set
	   */
	  public void selectSet(int active) {
		  
		  // selects the active drawing set
		  if ((active >= 0) && (active < sizeDrawing)) {
			  
			  // deselect any selected shape
			  deselect();
			  
			  // hide the current set
			  if (activeDrawing != active) {
				  drawingSets.get(activeDrawing).attr("stroke-width", 0);
				  labelSets.get(activeDrawing).attr("fill", "none");
			  }
		
			  
			  // show the new active set
			  activeDrawing = active;
			  drawingSets.get(activeDrawing).attr("stroke-width", 3);
			  labelSets.get(activeDrawing).attr("fill", "green");

		  }
		  
		  // init our drawing control variables
		  ghost= null;
		  drawingShape = null; 
		  drawMode = false;
			
		}

	  /**
	   * module resize - updates the viewBox data for the drawing to scale, zoom and pan
	   * @author debra willrett
	   */
	public void resize() {
		// not using resize yet, viewbox scales up the stroke width, so we need a better solution
		// also, have to get the size of the window, and getting it from the tool-bar is a hack
		double viewWidth = DOM.getElementById("imageView").getClientWidth();
		double viewHeight = DOM.getElementById("imageView").getClientHeight();
		double scalex = viewWidth/imageWidth;
		double scaley = viewHeight/imageHeight;
		this.scale = Math.max(scalex, scaley);
		this.windowWidth = this.imageWidth * this.scale;
		this.windowHeight = this.imageHeight * this.scale;

		console("drawing size " + this.windowWidth + " " +  this.windowHeight);
		this.setSize((int) this.windowWidth, (int) this.windowHeight);
	
		// we can scale with the viewbox, but isn't great for drawing because it scales up the stroke width
		//initViewBox();

		// we can import the iamge from the canvas, but don't do it just yet.
		//importCanvas();  
		
	}
	
	// used to get the window size
	private static native void  initViewBox() /*-{
	
		// get svg and set up viewBox
		var targetSvg = $doc.getElementsByTagName("svg");
	
		//console.log("targetSvg " + targetSvg[0] != null);
		targetSvg[0].setAttribute('viewBox', '0 0 512 512' );
		targetSvg[0].setAttribute('preserveAspectRatio', 'xMaxYMax meet' );  // xMidYMid meet,  xMinYMin meet

	}-*/;

	// used to get data from the canvas and into svg
	private static native void  importCanvas() /*-{
		
		// get canvas and svg
		var sourceCanvas = $doc.getElementById("canvas");
		var targetSvg = $doc.getElementsByTagName("svg");
	
		var svgNS = "http://www.w3.org/2000/svg";
        var xlinkNS = "http://www.w3.org/1999/xlink";

        // get base64 encoded png from Canvas
        var image = sourceCanvas.toDataURL();

        // must be careful with the namespaces
        var svgimg = $doc.createElementNS(svgNS, "image");

        svgimg.setAttribute('id', 'importedCanvas' );
        svgimg.setAttributeNS(xlinkNS, 'xlink:href', image);

        svgimg.setAttribute('x',  0);
        svgimg.setAttribute('y',  0);
        svgimg.setAttribute('width', sourceCanvas.width);
        svgimg.setAttribute('height', sourceCanvas.height);
    
        // pixel data needs to be saved because of firefox data:// url bug:
        // http://markmail.org/message/o2kd3bnnv3vcbwb2
        svgimg.imageData = sourceCanvas.toDataURL();
   
    	targetSvg[0].appendChild(svgimg);
    	
	}-*/;

	// output to firefox console
	private static native void console(String message) /*-{
    //console.clear();
    console.log(message);
	}-*/;
	
	
	/**
	* module addMouseDownHandler - allows the user to set up a down handler
	* @param active - the active drawing set
	*/
	public HandlerRegistration addMouseDownHandler(MouseDownHandler handler) {
        return this.addDomHandler(handler, MouseDownEvent.getType());
	}

	/**
	* module addMouseUpHandler - allows the user to set up an up handler
	* @param active - the active drawing set
	*/
	public HandlerRegistration addMouseUpHandler(MouseUpHandler handler) {
        return this.addDomHandler(handler, MouseUpEvent.getType());
	}

	/**
	* module addMouseMoveHandler - allows the user to set up a move handler
	* @param active - the active drawing set
	*/
	public HandlerRegistration addMouseMoveHandler(MouseMoveHandler handler) {
        return this.addDomHandler(handler, MouseMoveEvent.getType());
  	}

	/**
	* module addMouseOutHandler - allows the user to set up an out handler
	* @param active - the active drawing set
	*/
	public HandlerRegistration addMouseOutHandler(MouseOutHandler handler) {
        return this.addDomHandler(handler, MouseOutEvent.getType());
	}
  
	/**
	* module addMouseWheelHandler - allows the user to set up a  wheel handler
	* @param active - the active drawing set
	*/
	public HandlerRegistration addMouseWheelHandler(MouseWheelHandler handler) {
		return this.addDomHandler(handler, MouseWheelEvent.getType());
	}
	
	/**
	* module addKeyPressHandler - allows the user to set up a  key handler
	* @param active - the active drawing set
	*/
	public HandlerRegistration addKeyPressHandler(KeyPressHandler handler) {
		return this.addDomHandler(handler, KeyPressEvent.getType());
	}

	// delete the selected graphic
	private void deleteSelected() {
			// TODO Auto-generated method stub
			if (this.selected != null) {
				if (this.selected instanceof Line )	
				    ((Line) this.selected).getLabelShape().remove();
				else if (this.selected instanceof Rect )	
				    ((Rect) this.selected).getLabelShape().remove();
				else if (this.selected instanceof Circle )	
				    ((Circle) this.selected).getLabelShape().remove();
				else if (this.selected instanceof Ellipse )	
				    ((Ellipse) this.selected).getLabelShape().remove();
				else if (this.selected instanceof Poly )	
				    ((Poly) this.selected).getLabelShape().remove();
				else if (this.selected instanceof OpenPoly )	
				    ((OpenPoly) this.selected).getLabelShape().remove();
				else if (this.selected instanceof Arrow )	
				    ((Arrow) this.selected).getLabelShape().remove();
				this.selected.remove();
				
			}	
		}
	
	// deselect the selected graphic	
	private void clearSelected() {
			// TODO Auto-generated method stub
			if (this.selected != null) {
				this.selected.attr("stroke", "green");
				if (this.selected instanceof Line )	
				    ((Line) this.selected).getLabelShape().attr("fill", "green");
				else if (this.selected instanceof Rect )	
				    ((Rect) this.selected).getLabelShape().attr("fill", "green");
				else if (this.selected instanceof Circle )	
				    ((Circle) this.selected).getLabelShape().attr("fill", "green");
				else if (this.selected instanceof Ellipse )	
				    ((Ellipse) this.selected).getLabelShape().attr("fill", "green");
				else if (this.selected instanceof Poly )	
				    ((Poly) this.selected).getLabelShape().attr("fill", "green");
				else if (this.selected instanceof OpenPoly )	
				    ((OpenPoly) this.selected).getLabelShape().attr("fill", "green");
				else if (this.selected instanceof Arrow )	
				    ((Arrow) this.selected).getLabelShape().attr("fill", "green");
				
			}
			this.selected = null;
			
		}
	
	// select the graphic shape	
	private void setSelected(Shape shape) {
			clearSelected();
			this.selected = shape;
			this.selected.attr("stroke", "red");
			if (shape instanceof Line )	
			    ((Line) shape).getLabelShape().attr("fill", "red");
			else if (shape instanceof Rect )	
			    ((Rect) shape).getLabelShape().attr("fill", "red");
			else if (shape instanceof Circle )	
			    ((Circle) shape).getLabelShape().attr("fill", "red");
			else if (shape instanceof Ellipse )	
			    ((Ellipse) shape).getLabelShape().attr("fill", "red");
			else if (shape instanceof Poly )	
			    ((Poly) shape).getLabelShape().attr("fill", "red");
			else if (shape instanceof OpenPoly )	
			    ((OpenPoly) shape).getLabelShape().attr("fill", "red");
			else if (shape instanceof Arrow )	
			    ((Arrow) shape).getLabelShape().attr("fill", "red");
		}
	
	// get the selected shape
	private Shape getSelected() {

			return selected;	
		}
	
	// toggle the selected shape
	private void toggleSelected(Shape s) {
			if (getSelected().equals(s) && !ghost.equals(s)) 
		  	      clearSelected();
		  	 else 
		  	      setSelected(s);

		}
		
	private void startGhost(ROIShape shape, double x, double y) {
			// start drawing the ghost for a new shape
		switch (shape) {
		case  CIRCLE: 
			final Circle c = new Circle(x, y, 1);
			c.attr(lineAttr);
			setSelected(c);
			ghost = (Shape) c;
			c.addClickHandler(new ClickHandler() {
				public void onClick(ClickEvent e) {
					toggleSelected(c);}
				});	
			break;
		
		case RECTANGLE:
			final Rect r = new Rect(x, y, 1, 1);
			r.attr(lineAttr);
			setSelected(r);
			ghost = (Shape) r; 
			r.addClickHandler(new ClickHandler() {
			      public void onClick(ClickEvent e) {
			    	  toggleSelected(r);
			      }
			    });
			break;
			
		case OVAL:
			final Ellipse  ellipse = new Ellipse(x, y, 1, 1);
			ellipse.attr(lineAttr);
			setSelected(ellipse);
			ghost = (Shape) ellipse;
			ellipse.addClickHandler(new ClickHandler() {
			      public void onClick(ClickEvent e) {
			    	  toggleSelected(ellipse); 
			      }
			    });
			break;
			
		case LINE:
			final Line line = new Line(x, y, x + 1, y + 1);
			line.attr(lineAttr);
			setSelected(line);
			ghost = (Shape) line;
			line.addClickHandler(new ClickHandler() {
			      public void onClick(ClickEvent e) {
			    	  toggleSelected(line);
			      }
			    });
			break;
			
		case ARROW:
			final Arrow arrow = new Arrow(x, y, x + 1, y + 1);
			arrow.attr(lineAttr);
			setSelected(arrow);
			ghost = (Shape) arrow;
			arrow.addClickHandler(new ClickHandler() {
			      public void onClick(ClickEvent e) {
			    	  toggleSelected(arrow);
			      }
			    });
			break;
			
		case POLY:
			final Poly poly = new Poly(x, y);
			poly.attr(lineAttr);
			ghostPath = "M " + x + " " + y;
			setSelected(poly);
			ghost = (Shape) poly;
			poly.addClickHandler(new ClickHandler() { 
			      public void onClick(ClickEvent e) {
			    	  toggleSelected(poly);
			      }
			    }); 
			break;
			
		case OPENPOLY:	
			final OpenPoly openPoly = new OpenPoly(x, y);
			ghostPath = "M " + x + " " + y;
			openPoly.attr(lineAttr);
			setSelected(openPoly);
			ghost = (Shape) openPoly;
			openPoly.addClickHandler(new ClickHandler() { 
			      public void onClick(ClickEvent e) {
			    	  toggleSelected(openPoly);
			      }
			    }); 
			break;
		}
	}

	// update the ghost with the new points	
	private void updateGhost(double fromx, double fromy, double tox, double toy) {
		
		switch (drawingShape) {
		
		case  RECTANGLE: 
			ghost.attr("width", Math.abs(fromx - tox));
			ghost.attr("height", Math.abs(fromy - toy));
			((Rect) ghost).getLabelShape().position(tox,  toy);
			break;
			
		case  CIRCLE: 
			ghost.attr("cx", tox + (fromx - tox)/2);
			ghost.attr("cy", toy + (fromy - toy)/2);
			ghost.attr("r", (Math.abs(fromx - tox) + Math.abs(fromy - toy))/Math.PI);
			((Circle) ghost).getLabelShape().position(tox,  toy);
			break;

		case  LINE: 
			ghost.attr("path", "M" + fromx + " " + fromy + "L" + tox + " " + toy);
			((Line) ghost).getLabelShape().position(tox,  toy);
			break;

		case  ARROW: 
			int headlen = 10;   // length of head in pixels
		    double angle = Math.atan2(toy-fromy,tox-fromx);
			double a1x = tox-headlen*Math.cos(angle-Math.PI/6);
			double a1y = toy-headlen*Math.sin(angle-Math.PI/6);
			double a2x = tox-headlen*Math.cos(angle+Math.PI/6);
			double a2y = toy-headlen*Math.sin(angle+Math.PI/6);
			ghost.attr("path", "M" + fromx + " " + fromy + "L" + tox + " " + toy +  "L" + a1x + " " +  a1y + "M" + tox + " " + toy + "L" + a2x + " " +  a2y );
			((Arrow) ghost).getLabelShape().position(tox,  toy);
			break;
			
		case  OVAL:
			ghost.attr("cx", tox + (fromx - tox)/2);
			ghost.attr("cy", toy + (fromy - toy)/2);
			ghost.attr("rx", (Math.abs(fromx - tox))/2);
			ghost.attr("ry", (Math.abs(fromy - toy))/2);
			((Ellipse) ghost).getLabelShape().position(tox,  toy);
			break;
			
		case  POLY:
			ghost.attr("path", ghostPath +   " L "  +  tox + " " + toy + " ");
			((Poly) ghost).getLabelShape().position(tox,  toy);
			break;
			
		case  OPENPOLY:
			ghost.attr("path", ghostPath +   " L "  +  tox + " " + toy + " ");
			((OpenPoly) ghost).getLabelShape().position(tox,  toy);
			break;

		}
			
	}
		
	// add a point to the ghost
	private void pointGhost(double tox, double toy) {
		
		switch (drawingShape) {
		
		case  POLY: 
			ghostPath = ghostPath + " L "   +  tox + " " + toy + " ";
			ghost.attr("path", ghostPath);
			break;
		
		case OPENPOLY:
			ghostPath = ghostPath + " L "   +  tox + " " + toy + " ";
			ghost.attr("path", ghostPath);
			break;
		}
	}
			
		
		
	// some shapes require closing, like a closed poly
	private void closeGhost() {
		
		if (drawingShape == ROIShape.POLY) {
			ghostPath = ghostPath + " z ";
			ghost.attr("path", ghostPath);
		}	
	}
	
	
	/**
	* deselect any selected shape
	*/
	public void deselect() {
		clearSelected();
	}
		
	
		/**
		* startShape - start drawing a shape
		* @param double x - the mouse down x 
		* @param double y - the mouse down y 
		* @param ROIShape shape - the shape that is being drawn
		*/
		public void startShape(double x, double y, ROIShape shape) {
			
			if (!drawMode) {
				// start drawing mode
				drawMode = true;
				coords = new ArrayList<Coordinate>();
				Coordinate c = new Coordinate((int) x, (int) y);
				coords.add(c);
				drawingShape = shape;
				startGhost(shape, x, y);

			} else {
				// continue a multi-point shape
				Coordinate c = new Coordinate((int) x, (int) y);
				coords.add(c);
				pointGhost(x,y);

			}
			
		}
		
	/**
	* stopShape - determine if the shape drawing has completed
	* @param double fromx 
	* @param double fromy 
	* @param double tox 
	* @param double toy 
	* @return boolean stopShape - true if shape drawing has completed
	*/
	public boolean stopShape (double fromx, double fromy, double tox, double toy) {

			double deltaLastX = 0.0;
			double deltaLastY = 0.0;
			double deltaFirstX = 0.0;
			double deltaFirstY = 0.0;
			int size = coords.size();
			int first = 0, last = 0;
			if (size > 1) last = size - 2;
			boolean stop = true;
			
			if (drawingShape == ROIShape.OPENPOLY) {
				
				stop = false;
				if (size > 1) {
				
					// look for a double click to terminate
					deltaLastX = Math.abs(coords.get(last).getX() - tox);
					deltaLastY = Math.abs(coords.get(last).getY() - toy);

					stop =   (deltaLastX < MinDelta) && (deltaLastY < MinDelta);
					if (stop)  
						closeGhost();
					else 
						updateGhost(fromx, fromy, tox, toy);
				}
			} 
			
			if (drawingShape == ROIShape.POLY) {
				
				stop = false;
				if (size > 1) {

					// look for a click near the first click to terminate, or a double click
					deltaLastX = Math.abs(coords.get(last).getX() - tox);
					deltaLastY = Math.abs(coords.get(last).getY() - toy);
				
					deltaFirstX = Math.abs(coords.get(first).getX() - tox);
					deltaFirstY = Math.abs(coords.get(first).getY() - toy);
				
					stop =   ((deltaLastX < MinDelta) && (deltaLastY < MinDelta)) || ((deltaFirstX < MinDelta) && (deltaFirstY < MinDelta));
					if (stop)  
						closeGhost();
					else 
						updateGhost(fromx, fromy, tox, toy);
				}
				
			} 
			
			if (stop) drawMode = false;
			return stop;

		}
	
	/**
	* validShape - determine if the ghost is a valid shape
	* @return validShape - is the shape large enough to be a new shape, or is it just a click
	*/
	public boolean validShape() {
		
		boolean valid = false;
		if (ghost != null) {

			// make sure the ghost is big enough to become a shape 
			BBox b = ghost.getBBox();
			valid =  (b.width() > MinDelta || b.height() > MinDelta);
					
			if (!valid) {
				switch (drawingShape) {
				
				case LINE: 
				((Line) ghost).getLabelShape().remove();
				break;
				
				case RECTANGLE: 
				((Rect) ghost).getLabelShape().remove();
				break;
				
				case CIRCLE:
				((Circle) ghost).getLabelShape().remove();
				break;
				
				case OVAL:
				((Ellipse) ghost).getLabelShape().remove();
				break;
				
				case POLY:
				((Poly) ghost).getLabelShape().remove();
				break;
				
				case OPENPOLY:
				((OpenPoly) ghost).getLabelShape().remove();
				break;
				
				case ARROW:
				((Arrow) ghost).getLabelShape().remove();
				break;
				}
			
				
				ghost.remove();
				ghost = null;
				drawingShape = null;
			} 
		}
		return valid;
	}
	
	/**
	* getCoordsShape - return the coordinates of the multi-point shape
	* @return valid - is the shape large enough to be a new shape, or is it just a click
	*/

	public List<Coordinate> getCoordsShape () {
		
		return coords;
	}
	

		
	/**
	* module updateShape  - update the ghost drawing
	* @param fromx
	* @param fromy
	* @param tox
	* @param toy
	*/
	public void updateShape(double fromx, double fromy, double tox, double toy) {
			
			updateGhost(fromx, fromy, tox, toy);
	}
		
		
	/**
	* saveShape - save the shape to the drawing set for the active image
	*/
	public void saveShape () {

		if (ghost != null) { 
			switch (drawingShape) {
				case LINE:
					// set the attributes for the line and its label
					ghost.attr(lineAttr);
					((Line) ghost).getLabelShape().attr(labelAttr);
				
					// save both in their drawing set
					drawingSets.get(activeDrawing).push(ghost);
					labelSets.get(activeDrawing).push(((Line) ghost).getLabelShape());
					break;
		    	
				case RECTANGLE:
					// set the attributes for the line and its label
					ghost.attr(lineAttr);
					((Rect) ghost).getLabelShape().attr(labelAttr);
					
			    	// save both in their drawing set
					drawingSets.get(activeDrawing).push(ghost);
					labelSets.get(activeDrawing).push(((Rect) ghost).getLabelShape());
			    	break;
			    	
				case CIRCLE:
					// set the attributes for the line and its label
					ghost.attr(lineAttr);
					((Circle) ghost).getLabelShape().attr(labelAttr);
					
			    	// save both in their drawing set
					drawingSets.get(activeDrawing).push(ghost);
					labelSets.get(activeDrawing).push(((Circle) ghost).getLabelShape());
			    	break;
			    	
				case OVAL:
					// set the attributes for the line and its label
					ghost.attr(lineAttr);
					((Ellipse) ghost).getLabelShape().attr(labelAttr);
					
			    	// save both in their drawing set
					drawingSets.get(activeDrawing).push(ghost);
					labelSets.get(activeDrawing).push(((Ellipse) ghost).getLabelShape());
			    	break;
				case POLY:
					// set the attributes for the line and its label
					ghost.attr(lineAttr);
					((Poly) ghost).getLabelShape().attr(labelAttr);
					
			    	// save both in their drawing set
					drawingSets.get(activeDrawing).push(ghost);
					labelSets.get(activeDrawing).push(((Poly) ghost).getLabelShape());
			    	break;
				case OPENPOLY:
					// set the attributes for the line and its label
					ghost.attr(lineAttr);
					((OpenPoly) ghost).getLabelShape().attr(labelAttr);
					
			    	// save both in their drawing set
					drawingSets.get(activeDrawing).push(ghost);
					labelSets.get(activeDrawing).push(((OpenPoly) ghost).getLabelShape());
			    	break;
			    	
				case ARROW:
					// set the attributes for the line and its label
					ghost.attr(lineAttr);
					((Arrow) ghost).getLabelShape().attr(labelAttr);
					
			    	// save both in their drawing set
					drawingSets.get(activeDrawing).push(ghost);
					labelSets.get(activeDrawing).push(((Arrow) ghost).getLabelShape());
			    	break;
			}

		}
	}
	
	
	
	/**
	* deleteShape - delete the currently selected shape if there is one
	*/
	public void deleteShape () {

			if (selected != null)  {
				if (selected instanceof Line) {
					((Line) selected).getLabelShape().remove();
				}
				selected.remove();
				selected = null;
			}	 	
	}
	
	
	
	/**
	 * buildShape - build a shape for a particular set from a list of coords
	 * @param set - the drawing set for this shape
	 * @param shape - the shape to build
	 * @param coord - the list of coordinates that defines the shape
	 * 
	 */
	public void buildShape(int set, ROIShape shape, List<Coordinate> coord) {		
		
		logger.debug("buildShape " + set + shape);
		
		// make sure there are at least two coordinates and the set is available
		if ((coord.size() >= 2) && (set < sizeDrawing)) {
		
			// set up the drawing attributes to either show or hide
			Attr lineAttributes = new Attr();
			Attr labelAttributes = new Attr();
			lineAttributes = lineAttr;
			labelAttributes = labelAttr;
			if (set != activeDrawing) {
				lineAttributes.strokeWidth(0);
				labelAttributes.fill("none");
			}
	   
			double fromx = coord.get(0).getX();
			double fromy = coord.get(0).getY();
			double tox = coord.get(1).getX();
			double toy = coord.get(1).getY();
						
			double cx = Math.abs((tox - fromx)/2 + fromx);
			double cy = Math.abs((toy - fromy)/2 + fromy); 
			
			String path = "";
	    
			switch (shape) {
			case LINE:
				logger.debug("buildShape line " + set + shape);
				// create the line object and its label
				final Line line = new Line(fromx, fromy, tox, toy);
				line.getLabelShape().position(tox,  toy);
				
				// set the attributes for the line and its label
				line.attr(lineAttributes);
				line.getLabelShape().attr(labelAttributes);
				
				
		    	// save both in their drawing set
				drawingSets.get(set).push(line);
				labelSets.get(set).push(line.getLabelShape());

				// add the click handler	  
				line.addClickHandler(new ClickHandler() {
					public void onClick(ClickEvent e) {
					   toggleSelected(line);
					}
				});
				break;

			case RECTANGLE:
				// create the rectangle object
				double width = Math.abs(fromx - tox);
				double height = Math.abs(fromy - toy);
				final Rect rect = new Rect(fromx, fromy, width, height);
				rect.getLabelShape().position(tox,  toy);
				
				// set the attributes for the line and its label
				rect.attr(lineAttributes);
				rect.getLabelShape().attr(labelAttributes);
				
		    	// save both in their drawing set
				drawingSets.get(set).push(rect);
				labelSets.get(set).push(rect.getLabelShape());
				
							
				// add the click handler	  			
				rect.addClickHandler(new ClickHandler() {
					public void onClick(ClickEvent e) {
					  toggleSelected(rect);
				  	}
				});
				break;
				
			case CIRCLE:

				double r = Math.abs((tox - fromx)/2);
				final Circle circle = new Circle(cx, cy, r);
				circle.getLabelShape().position(tox,  toy);
				
				// set the attributes for the line and its label
				circle.attr(lineAttributes);
				circle.getLabelShape().attr(labelAttributes);
				
		    	// save both in their drawing set
				drawingSets.get(set).push(circle);
		    	labelSets.get(set).push(circle.getLabelShape());	
								
				// add the click handler
				circle.addClickHandler(new ClickHandler() {
						public void onClick(ClickEvent e) {
						    toggleSelected(circle);
						}
				});
				break;
				
			case OVAL:

				double rx = Math.abs((tox - fromx)/2);
				double ry = Math.abs((toy - fromy)/2); 
				final Ellipse  ellipse = new Ellipse(cx, cy, rx, ry);
				ellipse.getLabelShape().position(tox,  toy);
				
				// set the attributes for the line and its label
				ellipse.attr(lineAttributes);
				ellipse.getLabelShape().attr(labelAttributes);
				
		    	// save both in their drawing set
				drawingSets.get(set).push(ellipse);
				labelSets.get(set).push(ellipse.getLabelShape());	
				
				// add the click handler
				ellipse.addClickHandler(new ClickHandler() {
					      public void onClick(ClickEvent e) {
					    	  toggleSelected(ellipse); 
					      }
					    });
				break;
				
			case POLY:
				
				final Poly poly = new Poly(fromx, fromy);
				
				path = "M " + fromx + " " + fromy + " L ";
				for (int i = 1; i < coord.size(); i++ ){
					path = path +  " " + coord.get(i).getX() + " " + coord.get(i).getY()  + " ";
				}
				path = path + "z";
				poly.attr("path", path);
				
				// set the attributes for the line and its label
				poly.attr(lineAttributes);
				poly.getLabelShape().attr(labelAttributes);
				
		    	// save both in their drawing set
				drawingSets.get(set).push(poly);
				labelSets.get(set).push(poly.getLabelShape());	

				
				poly.addClickHandler(new ClickHandler() { 
				      public void onClick(ClickEvent e) {
				    	  toggleSelected(poly);
				      }
				    });
				break;
				
			case OPENPOLY:
				
				final OpenPoly openPoly = new OpenPoly(fromx, fromy);
				
				path = "M " + fromx + " " + fromy + " L ";
				for (int i = 1; i < coord.size(); i++ ){
						path = path +  " " + coord.get(i).getX() + " " + coord.get(i).getY()  + " ";
				}
				openPoly.attr("path", path);

				// set the attributes for the line and its label
				openPoly.attr(lineAttributes);
				openPoly.getLabelShape().attr(labelAttributes);
				
		    	// save both in their drawing set
				drawingSets.get(set).push(openPoly);
				labelSets.get(set).push(openPoly.getLabelShape());	

					
				openPoly.addClickHandler(new ClickHandler() { 
					 public void onClick(ClickEvent e) {
					    toggleSelected(openPoly);
					    }
					 });
				
				break;
				
			case ARROW:
				final Arrow arrow = new Arrow(fromx, fromy, tox, toy);
				arrow.getLabelShape().position(tox,  toy);
				
				
				// update the path for the arrow to add the head
				int headlen = 10;   // length of head in pixels
			    double angle = Math.atan2(toy-fromy,tox-fromx);
				double a1x = tox-headlen*Math.cos(angle-Math.PI/6);
				double a1y = toy-headlen*Math.sin(angle-Math.PI/6);
				double a2x = tox-headlen*Math.cos(angle+Math.PI/6);
				double a2y = toy-headlen*Math.sin(angle+Math.PI/6);
				arrow.attr("path", "M" + fromx + " " + fromy + "L" + tox + " " + toy +  "L" + a1x + " " +  a1y + "M" + tox + " " + toy + "L" + a2x + " " +  a2y );
				
				
				// set the attributes for the line and its label
				arrow.attr(lineAttributes);
				arrow.getLabelShape().attr(labelAttributes);
				
		    	// save both in their drawing set
				drawingSets.get(set).push(arrow);
				labelSets.get(set).push(arrow.getLabelShape());	

				
				// add the click handler
				arrow.addClickHandler(new ClickHandler() {
					      public void onClick(ClickEvent e) {
					    	  toggleSelected(arrow);
					      }
					    });
				break;
				
			}

		}
	}
	
	/**
	* startMove 
	*/
	public void startMove (double mouseX, double mouseY) {
			
		lastx = mouseX;
		lasty = mouseY;	
	}
	
	/**
	* updateMove 
	*/
	public void updateMove (double mouseX, double mouseY) {
		
		double dx = mouseX - lastx;
		double dy = mouseY - lasty;
		lastx = mouseX;
		lasty = mouseY;
		
		drawingSets.get(activeDrawing).translate(dx, dy);
		labelSets.get(activeDrawing).translate(dx, dy);	
	}
	
	/**
	* endMove 
	*/
	public void endMove (double mouseX, double mouseY) {

		updateMove(mouseX,mouseY);
		lastx = 0;
		lasty = 0;
	}
	
	/**
	* startRotate
	*/
	public void startRotate (double mouseX, double mouseY) {	
	}
	
	/**
	* updateRotate 
	*/
	public void updateRotate (double mouseX, double mouseY) {	
	}
	
	/**
	* endRotate  
	*/
	public void endRotate (double mouseX, double mouseY) {
	}
	
	/**
	* startZoom
	*/
	public void startZoom (double mouseX, double mouseY) {	
	}
	
	/**
	* updateZoom
	*/
	public void updateZoom (double mouseX, double mouseY) {	
	}
	
	/**
	* endZoom
	*/
	public void endZoom (double mouseX, double mouseY) {
		updateZoom (mouseX, mouseY);
	}
	
	
	
	

}
		
		
		
		

	
